Three.js - 封装

对three.js 的封装,建议使用ES6 Class

构造函数

在构造函数中实例化各种对象

javascript
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls"; //导入轨道控制器
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js";
import {
  CSS2DRenderer,
  CSS2DObject,
} from "three/addons/renderers/CSS2DRenderer.js";

export class ThreeScene {
  constructor(width, height, container) {
    // Step1. 实例化scene、camera、render、controls等对象
    this.scene = new THREE.Scene();
    this.camera = new THREE.PerspectiveCamera(50, width / height, 0.1, 1000);
    this.renderer = new THREE.WebGLRenderer({
      antialias: true,
      alpha: true,
    });
    this.controls = new OrbitControls(this.camera, this.renderer.domElement);
    
    // 这两个数组,用于保存模型与2D标签
    this.models = [];
    this.labels = [];

    // Step2. 实例化标签容器
    this.labelRenderer = new CSS2DRenderer();
    this.labelRenderer.setSize(width, height);
    this.labelRenderer.render(this.scene, this.camera);
    this.labelRenderer.domElement.style.position = "absolute";
    this.labelRenderer.domElement.style.top = "0";
    this.labelRenderer.domElement.style.left = "0";
    this.labelRenderer.domElement.style.pointerEvents = "none";
    container.appendChild(this.labelRenderer.domElement);

    // Step3. 初始化光源
    const directionalLight = new THREE.DirectionalLight(0xffffff, 1.4);
    directionalLight.position.set(30, 10, 20);
    this.scene.add(directionalLight);

    // Step4. 将 three.js的 DOM节点添加至指定节点下
    container.appendChild(this.renderer.domElement);

    // 构造函数结束
  }
}

加载模型与标签

javascript
export class Scene {
  constructor(width, height, container) {
    // 初始化
  }

  /**
   * @name loadModel
   * @description 加载模型
   * @param {String} path 模型文件所在路径(必须在public文件夹下)
   * @param {Function} callback (可选)加载完成后的回调,参数为Three.js模型实例
   */
  loadModel = (path, callback) => {
    loader.load(path, (gltf) => {
      this.scene.add(gltf.scene);
      this.models.push(gltf.scene);
      callback && callback(gltf.scene);
    });
  };

  /**
   * @name addLabel
   * @description 在指定模型上添加标签
   * @param {VNode} component 标签渲染的Vue组件实例
   * @param {THREE.Object3D} model Three.js模型
   * @param {Number} x x坐标
   * @param {Number} y y坐标
   * @param {Number} z z坐标
   */
  addLabel = (component, model, x, y, z) => {
    let label = new CSS2DObject(component);

    // 设置标签位置
    label.position.set(x, y, z);

    // 将标签添加到目标模型上
    model.add(label);
    this.labels.push(label);
  };
}

渲染、销毁、调整大小

javascript
export class Scene {
  constructor(width, height, container) {
    // 初始化
  }

  resize = (width, height) => {
    this.renderer.setSize(width, height);
    this.labelRenderer.setSize(width, height);
    this.camera.aspect = width / height;
    this.camera.updateProjectionMatrix();
    this.renderer.setPixelRatio(window.devicePixelRatio);
  };

  render = (func) => {
    const anime = () => {
      if (!this.renderer) {
        return;
      }
      this.labelRenderer?.render(this.scene, this.camera);
      this.controls.update();
      this.renderer.render(this.scene, this.camera);
      // 加了个回调
      func &&
        func({
          scene: this.scene,
          camera: this.camera,
          renderer: this.renderer,
          controls: this.controls,
          models: this.models,
          labels: this.labels,
        });
      requestAnimationFrame(anime);
    }
    anime();
  };

  destroy = () => {
    try {
      // 销毁Three.js实例
      renderer.dispose();
      renderer.forceContextLoss();
      renderer.content = null;
      let gl = renderer.domElement.getContext("webgl");
      if (gl && gl.getExtension("WEBGL_lose_context")) {
        gl.getExtension("WEBGL_lose_context").loseContext();
      }
      renderer = null;
      camera = null;
      scene.traverse((child) => {
        if (child.material) {
          child.material.dispose();
        }
        if (child.geometry) {
          child.geometry.dispose();
        }
        child = null;
      });
      scene = null;
    } catch (e) {
      console.error("Failed to destroy three.js instance", e);
    }
  };
}